home *** CD-ROM | disk | FTP | other *** search
-
- This is a FAQ answer on serial communications using the TTY protocol. It
- contains information on the TTY protocol and hardware and software implemen-
- tations on IBM PCs which is derived from National Semiconductor data sheets.
-
- PART ONE - HARDWARE & SOFTWARE
-
-
- Acknowledgements
- ================
-
- The following persons have contributed (directly or indirectly :-) to this
- summary:
- Madis Kaal <mast@anubis.kbfi.ee ??> this address is known to be bad
- Steve Poulsen <stevep@ims.com>
- Scott C. Sadow <NS16550A@mycro.UUCP>
- Dan Norstedt <?>
- [Commercial: This line could display YOUR name!]
-
-
-
- Introduction
- ============
-
- One of the most universal parts of the PC is its serial port. You can
- connect a mouse, a modem, a printer, a plotter, another PC, ...
- But its usage (both software and hardware) is one of the best-kept secrets
- for most users, besides that it is not difficult to understand how to
- connect (not plug-in) devices to it and how to program it.
- Regard this FAQ as a manual of the serial port of your PC for both
- hardware and software.
-
-
- Historical summary
- ------------------
-
- In early days of telecommunications, errand-boys and optical signals (flags,
- lights, clouds of smoke) were the only methods of transmitting information
- across long distances. With increasing requirements on speed and growing
- amount of information, more practical methods were developed. One milestone
- was the first wire-bound transmission on May 24th, 1844 ("What hath God
- wrought", using the famous Morse-alphabet). Well, technology improved a bit,
- and soon there were machines that could be used like typewriters, except that
- you typed not only on your own piece of paper but also on somebody elses.
- The only thing that has changed on the step from the teletyper to your PC
- is speed.
-
-
- The TTY (teletyping) protocol
- -----------------------------
-
- Definition: A protocol is a clear description of the LOGICAL method of
- transmitting information. This does NOT include physical realisation.
-
- The TTYp uses two different states of the line called 'mark' and 'space'.
- If no data is transmitted, the line is in the 'space' state. Data looks
- like
-
- space ----------+ +-------+ +---+ +-------
- | | | | | |
- mark +---+ +---+ +---+
-
- (1) --------(2)-------- (3)
-
- (1) start bit (2) data bits (3) stop bit(s)
-
- Both transmitter (TX) and receiver (RX) use the same data rate (measured
- in baud, which is the reciprocal value of the smallest time interval between
- two changes of the line state. TX and RX know about the number of data
- bits (probably with a parity bit added), and both know about the size of
- the stop step (called the stop bit or the stop bits, depending on the size
- of the stop step; normally 1, 1.5 or 2 times the size of a data bit). Data
- is transmitted bit-synchroneously and word-asynchroneously, which means that
- the size of the bits, the length of the word etc.pp. is clearly defined
- but the time between two words is undefined.
- The start bit indicates the beginning of a new data word. It is used to
- synchronize transmitter and receiver.
- Data is transmitted LSB to MSB, which means that the least significant
- bit (Bit 0) is transmitted first with 4 to 7 bits of data following, re-
- sulting in 5 to 8 bits of data. A logical '0' is transmitted by the
- 'space' state of the line, a logical '1' by 'mark'.
- A parity bit can be added to the data bits to allow error detection.
- There are two (well, actually five) kinds of parity: odd and even (plus
- none, mark and space). Odd parity means that the number of 'mark' steps in
- the data word (including parity bit) is always odd, so the parity bit is
- set accordingly (I don't have to explain 'even' parity, must I?). It is
- also possible to set the parity bit to a fixed state or to omit it.
- The stop bit does not indicate the end of the word (as it could be
- derived from its name); it rather separates two consecutive words by
- putting the line into the 'space' state for a minimum time.
- The protocol is usually described by a sequence of numbers and letters,
- e.g. 8n1 means 1 start bit (always), 8 bits of data, no parity bit, 1 stop
- bit. 7e2 would indicate 7 bits of data, even parity, 2 stop bits (but I've
- never seen this one...). The usual thing is 8n1 or 7e1.
- Early teletypers used the neckbreaking speed of 50 baud (which means
- that one step is 20ms), 5 bits of data, no parity and 1.5 stop bits (don't
- ask me why!). Your PC is capable of serial transmission at up to 115,200
- baud (step size of 8.68 microseconds!). Typical rates are 300 baud, 1200 baud,
- 2400 baud and 9600 baud.
-
-
- The physical transmission
- -------------------------
-
- Teletypers used a closed-loop line with a space current of 20ma and a
- mark current of 0ma (typical), which allowed to detect a 'broken line'.
- The RS232C port of your PC uses voltages rather than currents to indicate
- logical states: 'space' is signaled by +3v to +15v (typically +12v), 'mark'
- by -3v to -15v (typically -12V). The typical output impedance of the serial
- port of a PC is 2 kiloohms (resulting in about 5ma @ 10v), the typical input
- impedance is about 4.3 kiloohms, so there should be a maximum fan-out of 5
- (5 inputs can be connected to 1 output). Please don't rely on this, it may
- differ from PC to PC.
- Three lines (RX, TX & ground) are needed.
-
- Q. Why does my PC have a 25pin/9pin connector if there are only 3 lines
- needed?
- A. There are several status lines that are only used with a modem. See the
- software section of this FAQ.
-
- Q. How can I easily connect two PCs by a three-wire lead?
- A. This connection is called a 'null-modem' connection. RX1 is connected
- to TX2 and vice versa, GND1 to GND2. In addition to this, connect RTS
- to CTS & DCD and DTR to DSR (modem software often relies on that). See
- the hardware section for further details.
-
-
- Hardware
- ========
-
-
- The connectors
- --------------
-
- PCs have 9pin/25pin male SUB-D connectors. The pin layout is as follows
- (looking at the back side of your PC):
-
- 1 13 1 5
- _______________________________ _______________
- \ . . . . . . . . . . . . . / \ . . . . . /
- \ . . . . . . . . . . . . / \ . . . . /
- --------------------------- -----------
- 14 25 6 9
-
- Name (V24) 25pin 9pin Dir Full name Remarks
- --------------------------------------------------------------------------
- TxD 2 2 o Transmit Data
- RxD 3 3 i Receive Data
- RTS 4 5 o Request To Send
- CTS 5 8 i Clear To Send
- DTR 20 4 o Data Terminal Ready
- DSR 6 6 i Data Set Ready
- RI 22 9 i Ring Indicator
- DCD 8 1 i Data Carrier Detect
- GND 7 5 - Signal ground
- - 1 - - Protective ground Don't use this one!
- SCTE 24 - - Sync. clock trans. end Several PCs only
- SCT 15 - o Sync. clock TX dito.
- SCR 17 - i Sync. clock RX dito.
-
- The most important lines are RxD, TxD, and GND. Others are used with
- modems, printers and plotters to indicate internal states or to use
- synchroneous transmission (this is rarely used, and most PCs don't support
- it).
- '0' means +3v to +15V, '1' means -3v to -15v. '1' is the active state.
-
- The lines are:
-
- RxD, TxD: These lines carry the data.
- RTS, CTS: Are used by the PC and the modem/printer/whatsoever (further
- on referred to as the data set) to start/stop a communication. The PC
- sets RTS to its active state ('1'), and the data set responds with CTS
- '1' (always in this order). If the data set wants to stop/interrupt the
- communication (e.g. buffer overflow), it drops CTS to '0'; the PC uses
- RTS to control the data flow.
- DTR, DSR: Are used to establish a connection at the very beginning, i.e.
- the PC and the data set 'shake hands' first to assure they are both
- present. The PC sets DTR to '1', and the data set answers with DSR
- '1'. Modems often indicate hang-up by resetting DSR to '0'.
- (These six lines plus GND are often referred to as '7 wire'-connection or
- 'hand shake'-connection.)
- DCD: The modem uses this line to indicate that it has detected the
- carrier of the modem on the other side of the line.
- RI: The modem uses this line to signal that 'the phone rings' (even if
- there isn't a bell fitted to your modem).
- SCTE, SCT, SCR: forget about these.
- Protective ground: This line is connected to the power ground of the
- serial adapter. It should not be used as a signal ground, and it
- MUST NOT be connected to GND (even if your DMM shows up a
- connection!). Connect this line to the screen of the lead (if there is
- one).
-
- Technical data (typical):
-
- Signal level: -10.5v/+11v
- Short circuit current: 6.8ma (yes, that's enough for your mouse!)
- Output impedance: 2 kiloohms
- Input impedance: 4.3 kiloohms
-
-
- Connecting devices
- ------------------
-
- Normally, a 7 wire connection is used. Connect:
- GND1 to GND2
- RxD1 to TxD2
- TxD1 to RxD2
- DTR1 to DSR2
- DSR1 to DTR2
- RTS1 to CTS2
- CTS1 to RTS2
- If a modem is connected, add lines for the following:
- RI, DCD
- If software wants it, connect DCD1 to CTS1 and DCD2 to CTS2.
- BEWARE! While PCs use pin 2 for RxD and pin 3 for TxD, modems normally
- have those pins reversed! This allows to easily connect pin1 to pin1, pin2
- to pin 2 etc. If you connect two PCs, cross RxD and TxD.
-
- If hardware handshaking is not needed, a so-called null-modem connection
- can be used. Connect:
- GND1 to GND2
- RxD1 to TxD2
- TxD1 to RxD2
- Additionally, connect (if software needs it):
- RTS1 to CTS1 & DCD1
- RTS2 to CTS2 & DCD2
- DTR1 to DSR1
- DTR2 to DSR2
- You won't need long wires for these!
- The null-modem connection is used to establish an XON/XOFF-transmission
- between two PCs (see software section for details).
- Remember: the names DTR, DSR, CTS & RTS refer to the lines as seen from
- the PC. This means that for your data set DTR & RTS are incoming signals
- and DSR & CTS are outputs!
-
-
- Base addresses & interrupts
- ---------------------------
-
- Normally, the following list is correct:
-
-
- Port Base address Int #
-
- COM1 0x3F8 0xC
- COM2 0x2F8 0xB
- COM3 0x3E8 0xC
- COM4 0x2E8 0xB
-
-
- In PCs, serial communication is realized with a set of three chips
- (there are no further components needed!): a UART (Universal Asynchroneous
- Receiver/Transmitter) and two line drivers. Normally, the 82450/16450/8250
- does the 'brain work' while the 1488 and 1489 drive the lines.
- The chips are produced by many manufacturers; it's of no importance
- which letters are printed in front of the numbers (mostly NS for National
- Semiconductor). Don't regard the letters behind the number also; they just
- indicate special features and packaging (Advanced, FIFO, New, MILitary,
- bug fixes [see below] etc.).
- You might have heard of the possibility to replace the 16450 by a 16550A
- to improve reliability and software throughput. This is only useful if your
- software is able to use the FIFO (first in-first out) buffer features. The
- chips are fully pin-compatible except for two pins that are not used by
- any board known to the author: pin 24 (CSOUT, chip select out) and pin 29
- (NC, no connection to be made). With the 16550A, pin 24 is -TXRDY and pin
- 29 is -RXRDY, signals that aren't needed and that even won't care if they
- are shorted to +5v or ground. Therefore it should always be possible to
- simply replace the 16450 by the 16550A - even if it's not always useful due
- to lacking software capabilities. IT IS DEFINITELY NOT NECESSARY FOR
- COMMUNICATION UP TO LOUSY 9600 BAUD! These rates can easily be handled by
- any CPU and the interrupt-driven communication won't slow down the computer
- substantially. But if you want to use high-speed transfer with or without
- using the interrupt features (i.e. by 'polling'), it is recommended to use
- the 16550A in order to make transmission more reliable if your software
- supports it (see excursion some pages below).
-
-
- How to detect which chip is used
- --------------------------------
-
- This is really not difficult. The 8250 has no scratch register (see data
- sheet info below), the 16450/82450 has no FIFO, the 16550 has no working
- FIFO :-) and the 16550A performs alright. See the software section for
- an example.
-
-
- Data sheet information
- ----------------------
-
- Some hardware information taken from the data sheet of National
- Semiconductor (shortened and commented):
-
- Pin description of the 16450(16550A) [Dual-In-Line package]:
-
- +-----+ +-----+
- D0 -| 1 +-+ 40|- VCC
- D1 -| 2 39|- -RI
- D2 -| 3 38|- -DCD
- D3 -| 4 37|- -DSR
- D4 -| 5 36|- -CTS
- D5 -| 6 35|- MR
- D6 -| 7 34|- -OUT1
- D7 -| 8 33|- -DTR
- RCLK -| 9 32|- -RTS
- SIN -| 10 31|- -OUT2
- SOUT -| 11 30|- INTR
- CS0 -| 12 29|- NC (-RXRDY)
- CS1 -| 13 28|- A0
- -CS2 -| 14 27|- A1
- -BAUDOUT -| 15 26|- A2
- XIN -| 16 25|- -ADS
- XOUT -| 17 24|- CSOUT (-TXRDY)
- -WR -| 18 23|- DDIS
- WR -| 19 22|- RD
- VSS -| 20 21|- -RD
- +-------------+
-
- A0, A1, A2, Register Select, Pins 26-28:
- Address signals connected to these 3 inputs select a UART register for
- the CPU to read from or to write to during data transfer. A table of
- registers and their addresses is shown below. Note that the state of the
- Divisor Latch Access Bit (DLAB), which is the most significant bit of the
- Line Control Register, affects the selection of certain UART registers.
- The DLAB must be set high by the system software to access the Baud
- Generator Divisor Latches.
-
- DLAB A2 A1 A0 Register
- 0 0 0 0 Receive Buffer (read) Transmitter Holding Reg. (write)
- 0 0 0 1 Interrupt Enable
- x 0 1 0 Interrupt Identification (read)
- x 0 1 0 FIFO Control (write) (undefined on the 16450. CB)
- x 0 1 1 Line Control
- x 1 0 0 Modem Control
- x 1 0 1 Line Status
- x 1 1 0 Modem Status
- x 1 1 1 Scratch (special use on some boards. CB)
- 1 0 0 0 Divisor Latch (LSB)
- 1 0 0 1 Divisor Latch (MSB)
-
- -ADS, Address Strobe, Pin 25: The positive edge of an active Address
- Strobe (-ADS) signal latches the Register Select (A0, A1, A2) and Chip
- Select (CS0, CS1, -CS2) signals.
- Note: An active -ADS input is required when Register Select and Chip
- Select signals are not stable for the duration of a read or write
- operation. If not required, tie the -ADS input permanently low. (As it is
- done in your PC. CB)
-
- -BAUDOUT, Baud Out, Pin 15: This is the 16 x clock signal from the
- transmitter section of the UART. The clock rate is equal to the main
- reference oscillator frequency divided by the specified divisor in the
- Baud Generator Divisor Latches. The -BAUDOUT may also be used for the
- receiver section by tying this output to the RCLK input of the chip. (Yep,
- that's true for your PC. CB).
-
- CS0, CS1, -CS2, Chip Select, Pins 12-14: When CS0 and CS1 are high and CS2
- is low, the chip is selected. This enables communication between the UART
- and the CPU.
-
- -CTS, Clear To Send, Pin 36: When low, this indicates that the modem or
- data set is ready to exchange data. This signal can be tested by reading
- bit 4 of the MSR. Bit 4 is the complement of this signal, and Bit 0 is '1'
- if -CTS has changed state since the previous reading (bit0=1 generates an
- interrupt if the modem status interrupt has been enabled).
-
- D0-D7, Data Bus, Pins 1-8: Connected to the data bus of the CPU.
-
- -DCD, Data Carrier Detect, Pin 38: blah blah blah, can be tested by
- reading bit 7 / bit 3 of the MSR. Same text as -CTS.
-
- DDIS, Driver Disable, Pin 23: This goes low whenever the CPU is reading
- data from the UART.
-
- -DSR, Data Set Ready, Pin 37: blah, blah, blah, bit 5 / bit 1 of MSR.
-
- -DTR, Data Terminal Ready, Pin 33: can be set active low by programming
- bit 0 of the MCR '1'. Loop mode operation holds this signal in its
- inactive state.
-
- INTR, Interrupt, Pin 30: goes high when an interrupt is requested by the
- UART. Reset low by the MR.
-
- MR, Master Reset, Pin 35: Schmitt Trigger input, resets internal registers
- to their initial values (see below).
-
- -OUT1, Out 1, Pin 34: user-designated output, can be set low by
- programming bit 2 of the MCR '1' and vice versa. Loop mode operation holds
- this signal inactive high.
-
- -OUT2, Out 2, Pin 31: blah blah blah, bit 3. (Used in your PC to connect
- the UART to the interrupt line of the slot when '1'. CB)
-
- RCLK, Receiver Clock, Pin 9: This input is the 16 x baud rate clock for
- the receiver section of the chip. (Normally connected to -BAUDOUT, as in
- your PC. CB)
-
- RD, -RD, Read, Pins 22 and 21: When Rd is high *or* -RD is low while the
- chip is selected, the CPU can read data from the UART. (One of these is
- normally tied. CB)
-
- -RI, Ring Indicator, Pin 39: blah blah blah, Bit 6 / Bit 2 of the MSR.
- (Bit 2 indicates only change from active low to inactive high! Curious,
- isn't it? CB)
-
- -RTS, Request To Send, Pin 32: blah blah blah, see DTR (Bit 1).
-
- SIN, Serial Input, Pin 10.
-
- SOUT, Serial Output, Pin 11: ... Set to 'space' (high) upon MR.
-
- -RXRDY, -TYRDY: refer to NS data sheet. Those pins are used for DMA
- channeling. Since they are not connected in your PC, I won't describe them
- here.
-
- VCC, Pin 40, +5v
-
- VSS, Pin 20, GND
-
- WR, -WR: same as Rd, -RD for writing data.
-
- XIN, XOUT, Pins 16 and 17: Connect a crystal here (1.5k betw. xtal & pin 17)
- and pin 16 with a capacitor of approx. 20p to GND and other xtal conn. 40p
- to GND. Resistor of approx. 1meg parallel to xtal. Or use pin 16 as an input
- and pin 17 as an output for an external clock signal.
-
-
- Absolute Maximum Ratings:
-
- Temperature under bias: 0 C to +70 C
- Storage Temperature: -65 C to 150 C
- All input or output voltages with respect to VSS: -0.5v to +7.0v
- Power dissipation: 1W
-
- Further electrical characteristics see the very good data sheet of NS.
-
- UART Reset Configuration
-
- Register/Signal Reset Control Reset State
- --------------------------------------------------------------------
- IER MR 0000 0000
- IIR MR 0000 0001
- FCR MR 0000 0000
- LCR MR 0000 0000
- MCR MR 0000 0000
- LSR MR 0110 0000
- MSR MR xxxx 0000 (according to signals)
- SOUT MR high
- INTR (RCVR errs) Read LSR/MR low
- INTR (data ready) Read RBR/MR low
- INTR (THRE) Rd IIR/Wr THR/MR low
- INTR (modem status) Read MSR/MR low
- -OUT2 MR high
- -RTS MR high
- -DTR MR high
- -OUt1 MR high
- RCVR FIFO MR/FCR1&FCR0/DFCR0 all bits low
- XMIT FIFO MR/FCR1&FCR0/DFCR0 all bits low
-
-
-
- Known problems with several chips
- ---------------------------------
-
- (From material Madis Kaal received from Dan Norstedt)
-
- 8250 and 8250-B:
-
- * These UARTs pulse the INT line after each interrupt cause has
- been serviced (which none of the others do). [Generates interrupt
- overhead. CB]
-
- * The start bit is about 1 us longer than it ought to be. [This
- shouldn't be a problem. CB]
-
- * 5 data bits and 1.5 stop bits doesn't work.
-
- * When a 1 bit is written to the bit 1 (Tx int enab) in the IER,
- a Tx interrupt is generated. This is an erroneous interrupt
- if the THRE bit is not set. [So don't set this bit as long as
- the THRE bit isn't set. CB]
-
- * The first valid Tx interrupt after the Tx interrupt is enabled
- is probably missed. Suggested workaround:
- 1) Wait for the TRHE bit to become set.
- 2) Disable CPU interrupts.
- 3) Write Tx interrupt enable to the IER.
- 4) Write Tx interrupt enable to the IER, again.
- 5) Enable CPU interrupts.
-
- * The TSRE (bit 6) doesn't work properly.
-
- * If both the Rx and Tx interrupts are enabled, and a Rx interrupt
- occurs, the IIR indication may be lost; Suggested workarounds:
- 1) Test THRE bit in the Rx routine, and either set IER bit 1
- or call the Tx routine directly if it is set.
- 2) Test the THRE bit instead of using the IIR.
-
- * [If one of these chips vegetates in your PC, go get your solder
- iron heated... CB]
-
- 8250A, 82C50A, 16450 and 16C450:
-
- * (Same problem as above:)
- If both the Rx and Tx interrupts are enabled, and a Rx interrupt
- occurs, the IIR indication may be lost; Suggested workarounds:
- 1) Test THRE bit in the Rx routine, and either set IER bit 1
- or call the Tx routine directly if it is set.
- 2) Test the THRE bit instead of using the IIR.
- 3) [Don't enable both interrupts at the same time. I've never
- had any need to do this. CB]
-
- 16550 (without the A):
-
- * Rx FIFO bug: Sometimes a FIFO will get extra characters.
- [This seemed to be very embarracing for NS; they've added a
- simple detection method in the 16550A (bit 6 of IIR). CB]
-
- No bugs reported in the 16550A (yet?)
-
- [Same is true for the 16C552, a two-in-one version of the 16550A. CB]
-
-
-
- Software
- ========
-
-
- First some information from the data sheet. Then: HOW TO USE IT.
-
-
- Register Description
- --------------------
-
- See "Hardware" for addresses.
-
- Register Bit 0 Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7
-
- RBR (r/o) ----------------------- data bits received ------------------------
- THR (w/o) ------------------ data bits to be transmitted --------------------
- IER ERBFI ETBEI ELSI EDSSI 0 0 0 0
- IIR (r/o) pending IID0 IID1 IID2 0 0 FIFO en FIFOen
- FCR (w/o) enable RFres XFres DMAsel 0 0 - RX trigger -
- LCR - word length - stopbits PAR en even sel stick par SBR DLAB
- MCR DTR RTS OUT1 OUT2 Loop 0 0 0
- LSR RBF OE PE FE Break THRE TEMT FIFOerr
- MSR DCTS DDSR TERI DDCD CTS DSR RI DCD
-
- ERBFI: Enable Receiver Buffer Full Interrupt
- ETBEI: Enable Transmitter Buffer Empty Interrupt
- ELSI: Enable Line Status Interrupt
- EDSSI: Enable Delta Status Signals Interrupt
- IID#: Interrupt IDentification
- RFres: Receiver FIFO reset
- XFres: Transmitter FIFO reset
- SBR: Set BReak
- RBF: Receiver Buffer Full (Data Available)
- OE: Overrun Error
- PE: Parity Error
- FE: Framing Error
- THRE: Transmitter Holding Register Empty (new data can be written to THR)
- TEMT: Transmitter Empty (last word has been sent)
- DCTS: Delta Clear To Send
- DDSR: Delta Data Set Ready
- TERI: Trailing Edge Ring Indicator
- DDCD: Delta Data Carrier Detect
-
- LCR (Line Control Register):
-
- Bit 1 Bit 0 word length Bit 2 Stop bits
- 0 0 5 bits 0 1
- 0 1 6 bits 1 1.5/2
- 1 0 7 bits (1.5 if word length is 5)
- 1 1 8 bits (1.5 does not work with some chips, see above)
-
- Bit 5 Bit 4 Bit 3 Parity type Bit 6 SOUT condition
- x x 0 no parity 0 normal operation
- 0 0 1 odd parity 1 force 'mark' (break)
- 0 1 1 even parity Bit 7 DLAB
- 1 0 1 mark parity 0 normal registers
- 1 1 1 space parity 1 divisor at reg 0, 1
-
- Baud Rate Generator:
-
- DLAB must be set. Write word (16 bits) to address 0 of the UART (this is
- the base address) to program baud rate as follows:
- xtal frequency in Hz / 16 / rate = divisor
- Your PC uses an xtal frequency of 1.8432 MHz.
- Do *NOT* use 0 as a divisor (your maths teacher told you so)! [It
- results in a rate of some 1000 baud. CB]
- An error of up to 5 percent is irrelevant.
- Some values:
-
- Baud rate Divisor (hex) Percent Error
- 50 900 0.0%
- 75 600 0.0%
- 110 417 0.026%
- 134.5 359 0.058%
- 150 300 0.0%
- 300 180 0.0%
- 600 C0 0.0%
- 1200 60 0.0%
- 1800 40 0.0%
- 2000 3A 0.69%
- 2400 30 0.0%
- 3600 20 0.0%
- 4800 18 0.0%
- 7200 10 0.0%
- 9600 C 0.0%
- 19200 6 0.0%
- 38400 3 0.0%
- 56000 2 2.86%
- 115200 1 0.0%
-
- NS specifies that the 16550A is capable of 256 kbaud if you use a 4 MHz
- or an 8 MHz crystal. But a staff member of NS Germany (I know that this
- abbreviation is not well-chosen :-( ) told one of my friends on the phone
- that it runs correctly at 512 kbaud as well, but I don't know if the
- 1488/1489 manage this. This is true for the 16C552, too.
- BTW: Ever tried 1.76 baud? Kindergarten kids write faster.
- Mice typically use 2400 baud, 8n1.
-
-
- LSR (Line Status Register):
-
- Bit 0 Data Ready (DR). Reset by reading RBR.
- Bit 1 Overrun Error (OE). Reset by reading LSR. Indicates loss of data.
- Bit 2 Parity Error (PE). Indicates transmission error. Reset by LSR.
- Bit 3 Framing Error (FE). Indicates missing stop bit. Reset by LSR.
- Bit 4 Break Indicator (BI). Set if 'space' for more than 1 word. Reset
- by LSR.
- Bit 5 Transmitter Holding Register Empty (THRE). Indicates that a new
- word can be written to THR. Reset by writing THR.
- Bit 6 Transmitter Empty (TEMT). Indicates that no transmission is
- running. Reset by reading LSR.
- Bit 7 Set if at least 1 word in FIFO has been received with an error.
- Cleared by reading LSR if there is no further error in the FIFO.
-
- FCR (FIFO Control Register):
-
- Bit 0: FIFO enable.
- Bit 1: Clear receiver FIFO. This bit is self-clearing.
- Bit 2: Clear transmitter FIFO. This bit is self-clearing.
- Bit 3: DMA mode (pins -RXRDY and -TXRDY), see sheet
- Bits 6-7:Trigger level of the DR-interrupt.
- Bit 7 Bit 6 Receiver FIFO trigger level
- 0 0 01
- 0 1 04
- 1 0 08
- 1 1 14
-
-
- Excursion: why and how to use the FIFOs (by Scott C. Sadow)
- -----------------------------------------------------------
-
- Normally when transmitting or receiving, the UART generates an
- interrupt for every character sent or received. For 2400 baud, typically
- this is 240/second. For 115,200 baud, this means 11,520/second. With FIFOs
- enabled, the number of interrupts is greatly reduced. For transmit
- interrupts, the UART indicates the transmit holding register is not busy
- until the 16 byte FIFO is full. A transmit hold register empty interrupt
- is not generated until the FIFO is empty (last byte is being sent) Thus,
- the number of transmit interrupts is reduced by a factor of 16. For
- 115,200 baud, this means only 7,200 interrupts/second. For receive data
- interrupts, the processing is similar to transmit interrupts. The main
- difference is that the number of bytes in the FIFO before generating an
- interrupt can be set. When the trigger level is reached, a recieve data
- interrupt is generated, but any other data received is put in the FIFO.
- The receive data interrupt is not cleared until the number of bytes in the
- FIFO is below the trigger level.
-
- To add 16550A support to existing code, there are 2 requirements.
-
- 1) When reading the IIR to determine the interrupt source, only
- use the lower 3 bits.
-
- 2) After the existing UART initialization code, try to enable the
- FIFOs by writing to the FCR. (A value of C7 hex will enable FIFO mode,
- clear both FIFOs, and set the receive trigger level at 14 bytes) Next,
- read the IIR. If Bit 6 of the IIR is not set, the UART is not a
- 16550A, so write 0 to the FCR to disable FIFO mode.
-
-
- IIR (Interrupt Identification Register):
-
- Bit 3 Bit 2 Bit 1 Bit 0 Priority Source Description
- 0 0 0 1 none
- 0 1 1 0 highest Status OE, PE, FE or BI of the
- LSR set. Serviced by
- reading the LSR.
- 0 1 0 0 second Receiver DR or trigger level rea-
- ched. Serviced by read-
- ing RBR 'til under level
- 1 1 0 0 second FIFO No Receiver FIFO action
- since 4 words' time
- (neither in nor out) but
- data in RX-FIFO. Serviced
- by reading RBR.
- 0 0 1 0 third Transm. THRE. Serviced by read-
- ing IIR (if source of
- int only!!) or writing
- to THR.
- 0 0 0 0 fourth Modem One of the delta flags
- in the MSR set. Serviced
- by reading MSR.
- Bit 6 & 7: 16550A: set if FCR bit 0 set.
- 16550: bit 7 set, bit 6 cleared
- others: clear
- In most software applications bits 3, 6 & 7 should be masked when servicing
- the interrupt since they are not relevant. These bits cause trouble with
- old software relying on that they are cleared...
- NOTE! Even if some of these interrupts are masked, the service routine
- can be confronted with *all* states shown above when the IIR is loop-polled
- until bit 0 is set. Check examples.
-
- IER (Interrupt Enable Register):
-
- Bit 0: If set, DR interrupt is enabled.
- Bit 1: If set, THRE interrupt is enabled.
- Bit 2: If set, Status interrupt is enabled.
- Bit 3: If set, Modem status interrupt is enabled.
-
- MCR (Modem Control Register):
-
- Bit 0: Programs -DTR. If set, -DTR is low and the DTR pin of the port is
- '1'.
- Bit 1: Programs -RTS.
- Bit 2: Programs -OUT1. Not used in a PC.
- Bit 3: Programs -OUT2. If set, interrupts generated by the UART are trans-
- ferred to the ICU (Interrupt Control Unit).
- Bit 4: '1': local loopback. All outputs disabled.
-
- MSR (Modem Status Register):
-
- Bit 0: Delta CTS. Set if CTS has changed state since last reading.
- Bit 1: Delta DSR. Set if DSR has changed state since last reading.
- Bit 2: TERI. Set if -RI has changed from low to high (i.e. RI at port
- has changed from '1' to '0').
- Bit 3: Delta DCD. Set if DCD has changed state since last reading.
- Bit 4: CTS. 1 if '1' at port.
- Bit 5: DSR.
- Bit 6: RI. If loopback is selected, it is equivalent to OUT1.
- Bit 7: DCD.
-
-
- PART TWO - PROGRAMMING
-
-
- Programming
- -----------
-
- Now for the clickety-clickety thing. I hope you're a bit keen in
- assembler programming (if not, you've got a problem B-). Programming the UART
- in high level languages is, of course, possible, but not at very high
- rates or interrupt-driven. I give you several routines in assembler (and,
- wherever possible, in C) that do the dirty work for you.
-
- First thing to do is detect which chip is used. It shouldn't be difficult
- to convert this C function into assembler; I'll omit the assembly version.
-
- int detect_UART(unsigned baseaddr)
- {
- // this function returns 0 if no UART is installed.
- // 1: 8250, 2: 16450, 3: 16550, 4: 16550A
- int x;
- // first step: see if the LCR is there
- outp(baseaddr+3,0x1b);
- if (inp(baseaddr+3)!=0x1b) return 0;
- outp(baseaddr+3,0x3);
- if (inp(baseaddr+3)!=0x3) return 0;
- // next thing to do is look for the scratch register
- outp(baseaddr+7,0x55);
- if (inp(baseaddr+7)!=0x55) return 1;
- outp(baseaddr+7,0xAA);
- if (inp(baseaddr+7)!=0xAA) return 1;
- // then check if there's a FIFO
- outp(baseaddr+2,0x1);
- x=inp(baseaddr+2);
- if ((x&0x80)==0) return 2;
- if ((x&0x40)==0) return 3;
- return 4;
- }
-
- Remember: if it's not a 16550A, don't use the FIFO mode!
-
-
- Now the non-interrupt version of TX and RX.
-
- Let's assume the following constants are set correctly (either by
- 'CONSTANT EQU value' or by '#define CONSTANT value'). You can easily use
- variables instead, but I wanted to save the extra lines for the ADD
- commands necessary then...
-
- UART_BASEADDR the base address of the UART
- UART_BAUDRATE the divisor value (e.g. 12 for 9600 baud)
- UART_LCRVAL the value to be written to the LCR (e.g. 0x1b for 8N1)
- UART_FCRVAL the value to be written to the FCR. Bit 0, 1 and 2 set,
- bits 6 & 7 according to trigger level wished (see above).
- 0x87 is a good value.
-
- First thing to do is initializing the UART. This works as follows:
-
- init_UART proc near
- push ax ; we are 'clean guys'
- push dx
- mov dx,UART_BASEADDR+3 ; LCR
- mov al,80h ; set DLAB
- out dx,al
- mov dx,UART_BASEADDR ; divisor
- mov ax,UART_BAUDRATE
- out dx,ax
- mov dx,UART_BASEADDR+3 ; LCR
- mov al,UART_LCRVAL ; params
- out dx,al
- mov dx,UART_BASEADDR+4 ; MCR
- xor ax,ax ; clear loopback
- out dx,al
- ;***
- pop dx
- pop ax
- ret
- init_UART endp
-
- void init_UART()
- {
- outp(UART_BASEADDR+3,0x80);
- outpw(UART_BASEADDR,UART_BAUDRATE);
- outp(UART_BASEADDR+3,UART_LCRVAL);
- outp(UART_BASEADDR+4,0);
- //***
- }
-
- If we wanted to use the FIFO functions of the 16550A, we'd have to add
- some lines to the routines above (where the ***s are).
- In assembler:
- mov dx,UART_BASEADDR+2 ; FCR
- mov al,UART_FCRVAL
- out dx,al
- And in C:
- outp(UART_BASEADDR+2,UART_FCRVAL);
-
- Don't forget to disable the FIFO when your program exits! Some other
- software may rely on this!
-
- Not very complex so far, isn't it? Well, I told you so at the very
- beginning, and we wanted to start easy. Now let's send a character.
-
- UART_send proc near
- ; character to be sent in AL
- push dx
- push ax
- mov dx,UART_BASEADDR+5
- us_wait:
- in al,dx ; wait until we are allowed to write a byte to the THR
- test al,20h
- jz us_wait
- pop ax
- mov dx,UART_BASEADDR
- out dx,al ; then write the byte
- pop ax
- pop dx
- ret
- UART_send endp
-
- void UART_send(char character)
- {
- while ((inp(UART_BASEADDR+5)&0x20)!=0) {;}
- outp(UART_BASEADDR,(int)character);
- }
-
- This one sends a null-terminated string.
-
- UART_send_string proc near
- ; DS:SI contains a pointer to the string to be sent.
- push si
- push ax
- push dx
- cld ; we want to read the string in its correct order
- uss_loop:
- lodsb
- or al,al ; last character sent?
- jz uss_ende
- ;*1*
- mov dx,UART_BASEADDR+5
- push ax
- uss_wait:
- in al,dx
- test al,20h
- jz uss_wait
- mov dx,UART_BASEADDR
- pop ax
- out dx,al
- ;*2*
- jmp uss_loop
- uss_ende:
- pop dx
- pop ax
- pop si
- ret
- UART_send_string endp
-
- void UART_send_string(char *string)
- {
- int i;
- for (i=0; string[i]!=0; i++)
- {
- //*1*
- while ((inp(UART_BASEADDR+5)&0x20)!=0) {;}
- outp(UART_BASEADDR,(int)string[i]);
- //*2*
- }
- }
-
- Of course, we could have used our already programmed function/procedure
- UART_send instead of the piece of code limited by *1* and *2*, but we are
- interested in high-speed code.
-
- It shouldn't be a hard nut for you to modify the above function/procedure
- so that it sends a block of data rather than a null-terminated string. I'll
- omit that here.
-
- Now for reception. We want to program routines that do the following:
- - check if a character received or an error occured
- - read a character if there's one available
-
- Both the C and the assembler routines return 0 (in AX) if there is
- neither an error condition nor a character available. If a character is
- available, Bit 8 is set and AL or the lower byte of the return value
- contains the character. Bit 9 is set if we lost data (overrun), bit 10
- signals a parity error, bit 11 signals a framing error, bit 12 shows if
- there is a break in the data stream and bit 15 signals if there are any
- errors in the FIFO (if we turned it on). The procedure/function is much
- smaller than this paragraph:
-
- UART_get_char proc near
- push dx
- mov dx,UART_BASEADDR+5
- in al,dx
- mov ah,al
- and ah,9fh
- test al,1
- jz ugc_nochar
- mov dx,UART_BASEADDR
- in al,dx
- ugc_nochar:
- pop dx
- ret
- UART_get_char endp
-
- unsigned UART_get_char()
- {
- unsigned x;
- x=(inp(UART_BASEADDR+5)<<8)&0x9f;
- if (x&0x100) x|=((unsigned)inp(UART_BASEADDR))&0xff);
- return x;
- }
-
- This procedure/function lets us easily keep track of what's happening
- with the RxD pin. It does not provide any information on the modem status
- lines! We'll program that later on.
-
- If we wanted to show what's happening with the RxD pin, we'd just have to
- write a routine like the following (I use a macro in the assembler version
- to shorten the source code):
-
- DOS_print macro pointer
- ; prints a string in the code segment
- push ax
- push ds
- push cs
- pop ds
- mov dx,pointer
- mov ah,9
- int 21h
- pop ds
- pop ax
- endm
-
- UART_watch_rxd proc near
- uwr_loop:
- ; check if keyboard hit; we want a possibility to break the loop
- mov ah,1
- int 16h
- jnz uwr_exit
- call UART_get_char
- or ax,ax
- jz uwr_loop
- test ah,1 ; is there a character in AL?
- jz uwr_nodata
- push ax ; yes, print it
- mov dl,al
- mov ah,2
- int 21h
- pop ax
- uwr_nodata:
- test ah,0eh ; any error at all?
- jz uwr_loop ; this speeds up things
- test ah,2 ; overrun error?
- jz uwr_noover
- DOS_print overrun_text
- uwr_noover:
- test ah,4 ; parity error?
- jz uwr_nopar
- DOS_print parity_text
- uwr_nopar:
- test ah,8 ; framing error?
- jz uwr_loop
- DOS_print framing_text
- jmp uwr_loop
- overrun_text db "*** Overrun Error ***$"
- parity_text db "*** Parity Error ***$"
- framing_text db "*** Framing Error ***$"
- UART_watch_rxd endp
-
- void UART_watch_rxd()
- {
- union _useful_
- {
- unsigned val;
- char character;
- } x;
- while (!kbhit())
- {
- x.val=UART_get_char();
- if (!x.val) continue; // nothing? Continue
- if (x.val&0x100) putc(x.character); // character? Print it
- if (!(x.val&0x0e00)) continue; // any error condidion? No, continue
- if (x.val&0x200) printf("*** Overrun Error ***");
- if (x.val&0x400) printf("*** Parity Error ***");
- if (x.val&0x800) printf("*** Framing Error ***");
- }
- }
-
- If you call these routines from a function/procedure as shown below,
- you've got a small terminal program!
-
- terminal proc near
- ter_loop:
- call UART_watch_rxd ; watch line until a key is pressed
- xor ax,ax ; get that key from the buffer
- int 16h
- cmp al,27 ; is it ESC?
- jz ter_end ; yes, then end this function
- call UART_send ; send the character typed if it's not ESC
- jmp ter_loop ; don't forget to check if data comes in
- ter_end:
- ret
- terminal endp
-
- void terminal()
- {
- int key;
- while (1)
- {
- UART_watch_rxd();
- key=getche();
- if (key==27) break;
- UART_send((char)key);
- }
- }
-
- These, of course, should be called from an embedding routine like the
- following (the assembler routines concatenated will assemble as an .EXE
- file. Put the lines 'code segment' and 'assume cs:code,ss:stack' to the
- front).
-
- main proc near
- call UART_init
- call terminal
- mov ax,4c00h
- int 21h
- main endp
- code ends
- stack segment stack 'stack'
- dw 128 dup (?)
- stack ends
- end main
-
- void main()
- {
- UART_init();
- terminal();
- }
-
- Here we are. Now you've got everything you need to program null-modem
- polling UART software.
- You know the way. Now go and add functions to check if a data set is
- there, then establish a connection. Don't know how? Set DTR, wait for DSR.
- If you want to send, set RTS and wait for CTS before you actually transmit
- data. You don't need to store old values of the MCR: this register is
- readable. Just read in the data, AND/OR the bit required and write the
- byte back.
-
-
- Now for the interrupt-driven version of the program. This is going to be
- a bit voluminous, so I draw the scene and leave the painting to you. If you
- want to implement interrupt-driven routines in a C program use either the
- inline-assembler feature or link the objects together.
-
- First thing to do is initialize the UART the same way as shown above.
- But there is some more work to be done before you enable the UART
- interrupt: FIRST SET THE INTERRUPT VECTOR CORRECTLY! Use Function 0x25 of
- the DOS interrupt 0x21. See also the note on known bugs if you've got a
- 8250.
-
- UART_INT EQU 0Ch ; for COM2 / COM4 use 0bh
- UART_ONMASK EQU 11101111b ; for COM2 / COM4 use 11110111b
- UART_OFFMASK EQU 00010000b ; for COM2 / COM4 use 00001000b
- UART_IERVAL EQU ? ; replace ? by any value between 0h and 0fh
- ; (dependent on which ints you want)
- ; DON'T SET bit 1 yet!
-
- initialize_UART_interrupt proc near
- push ds
- push cs ; build a pointer in DS:DX
- pop ds
- lea dx,interrupt_service_routine
- mov ax,2500h+UART_INT
- int 21h
- pop ds
- mov dx,UART_BASEADDR+4 ; MCR
- in al,dx
- or al,8 ; set OUT2 bit to enable interrupts
- out dx,al
- mov dx,UART_BASEADDR+1 ; IER
- mov al,UART_IERVAL
- out dx,al
- in al,21h ; last thing to do is unmask the int in the ICU
- and al,UART_ONMASK
- out 21h,al
- sti ; and free interrupts if they have been disabled
- ret
- initialize_UART_interrupt endp
-
- Now the interrupt service routine. It has to follow several rules:
- first, it MUST NOT change the contents of any register of the CPU! Then it
- has to tell the ICU (did I tell you that this is the interrupt control
- unit?) that the interrupt is being serviced. Next thing is test which part
- of the UART needs service. Let's have a look at the following procedure:
-
- interupt_service_routine proc far ; define as near if you want to link .COM
- ;*1*
- push ax
- push cx
- push dx
- push bx
- push sp
- push bp
- push si
- push di
- ;*2* replace the part between *1* and *2* by pusha on an 80186+ system
- push ds
- push es
- mov al,20h ; remember: first thing to do in interrupt routines is tell
- out 20h,al ; the ICU about it. This avoids lock-up
- int_loop:
- mov dx,UART_BASEADDR+2 ; IIR
- xor ax,ax ; clear AH; this is the fastest and shortest possibility
- in al,dx ; check IIR info
- test al,1
- jnz int_end
- and al,6 ; we're interested in bit 1 & 2 (see data sheet info)
- mov si,ax ; this is already an index! Well-devised, huh?
- call word ptr cs:int_servicetab[si] ; ensure a near call is used...
- jmp int_loop
- int_end:
- pop es
- pop ds
- ;*3*
- pop di
- pop si
- pop bp
- pop sp
- pop bx
- pop dx
- pop cx
- pop ax
- ;*4* *3* - *4* can be replaced by popa on an 80186+ based system
- iret
- interupt_service_routine endp
-
- This is the part of the service routine that does the decisions. Now we
- need four different service routines to cover all four interrupt source
- possibilities (EVEN IF WE DIDN'T ENABLE THEM!! Since 'unexpected'
- interrupts can have higher priority than 'expected' ones, they can appear
- if an expected [not masked] interrupt situation shows up).
-
- int_servicetab DW int_modem, int_tx, int_rx, int_status
-
- int_modem proc near
- mov dx,UART_BASE+6 ; MSR
- in al,dx
- ; do with the info what you like; probably just ignore it...
- ; but YOU MUST READ THE MSR or you'll lock up the system!
- ret
- int_modem endp
-
- int_tx proc near
- ; get next byte of data from a buffer or something
- ; (remember to set the segment registers correctly!)
- ; and write it to the THR (offset 0)
- ; if no more data is to be sent, disable the THRE interrupt
- ; If the FIFO is used, you can write data as long as bit 5
- ; of the LSR is 0
-
- ; end of data to be sent?
- ; no, jump to end_int_tx
- mov dx,UART_BASEADDR+1
- in al,dx
- and al,00001101b
- out dx,al
- end_int_tx:
- ret
- int_tx endp
-
- int_rx proc near
- mov dx,UART_BASEADDR
- in al,dx
- ; do with the character what you like (best write it to a
- ; FIFO buffer)
- ; the following lines speed up FIFO mode operation
- mov dx,UART_BASEADDR+5
- in al,dx
- test al,1
- jnz int_rx
- ret
- int_rx endp
-
- int_status proc near
- mov dx,UART_BASEADDR+5
- in al,dx
- ; do what you like. It's just important to read the LSR
- ret
- int_status endp
-
- How is data sent now? Write it to a FIFO buffer that is read by the
- interrupt routine. Then set bit 1 of the IER and check if this has already
- started transmission. If not, you'll have to start it by yourself... THIS
- IS DUE TO THOSE NUTTY GUYS AT BIG BLUE WHO DECIDED TO USE EDGE TRIGGERED
- INTERRUPTS INSTEAD OF PROVIDING ONE SINGLE FLIP FLOP FOR THE 8253/8254!
- This procedure can be a C function, too. It is not time-critical at all.
-
- ; copy data to buffer
- mov dx,UART_BASEADDR+1 ; IER
- in al,dx
- or al,2 ; set bit 1
- out dx,al
- mov dx,UART_BASEADDR+5 ; LSR
- in al,dx
- test al,40h ; is there a transmission running?
- jz dont_crank ; yes, so don't mess it up
- call int_tx ; no, crank it up
- dont_crank:
-
- Well, that's it! Your main program has to take care about the buffers,
- nothing else!
-
- One more thing: always remember that at 115,200 baud there is service to
- be done at least every 8 microseconds! On an XT with 4.77 MHz this is
- about 5 assembler commands!! So forget about servicing the serial port at
- this rate in an interrupt-driven manner on such computers. An AT with 12
- MHz probably will manage it if you use 'macro commands' such as pusha and/or
- a 16550A in FIFO mode. An AT can perform about 20 instructions between two
- characters, a 386 with 25 MHz will do about 55, and a 486 with 33 MHz will
- manage about 150. Using a 16550A is strongly recommended at high rates.
- The interrupt service routines can be accelerated by not pushing that
- much registers, and pusha and popa are fast instructions.
-
- Another last thing: due to the poor construction of the PC interrupt
- system, one interrupt line can only be driven by one device. This means if
- you want to use COM3 and your mouse is connected to COM1, you can't use
- interrupt features without disabling the mouse (write 0x0 to the mouse's
- MCR).